package com.ordobill.webapp.engine;

import java.util.ArrayList;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ordobill.webapp.beans.FigInform;
import com.ordobill.webapp.beans.Figures;
import com.ordobill.webapp.beans.Modeling;
import com.ordobill.webapp.beans.Project;
import com.ordobill.webapp.beans.SimulationFields;

import org.apache.commons.math3.distribution.NormalDistribution;

/**
 * Simulation Engine에 필요한 것들을 구현하고 정의하는 클래스입니다.
 * TypeOfFail와 TypeOfRepair 인터페이스를 상속 구현하였고, 실제 Engine에서 구현해야 하는
 * SimulationBody 추상 메소드도 정의 되어있습니다.
 * 
 * @author Choi Jin Wook(A.K.A Brian Choi @ Ordobill Office) / choijinwook84@gmail.com
 * @version $ID: SimulationProp.java R365v
 *
 */
public abstract class SimulationEngine implements TypeOfFailure, TypeOfRepair{
    
    /**
     * SimualtionProp의 생성자를 정의합니다.
     * Project Bean 을 Parameter 로 받아 SimulationProp의 기본 변수들을
     * 초기화 합니다.
     * @param Project
     */
    public SimulationEngine(Project project){
        setSimTime(project.getProIterNum());
        setStrTime(project.getProStartTime());
        setLifeTime(project.getProLifeTime());
        setTimeDiv(project.getProTd());
    }
    
    /**
     * 기초 생성자입니다.
     */
    public SimulationEngine(){}
    
    /**
     * Simulation Time
     * */
    private int simTime;
    
    /**
     * Starting time of Simulation(years) 
     */
    private int strTime;
    
    /**
     * Life Time(years)
     */
    private int lifeTime;

    /**
     * Time Division(times)
     */
    private float timeDiv;
    
    /**
     * Initialization from SimulationEngine 
     */
    private float present;
    
    /**
     * For Information figures when simulation each by
     */
    private ArrayList<FigInform> figInformList = new ArrayList<FigInform>();
    
    /**
     * Accessor the simulation time
     * 
     * @return The simulation time
     */
    public int getSimTime() {
        return simTime;
    }
    
    /**
     * Mutator the simulation time
     * 
     * @param simTime
     */
    public void setSimTime(int simTime) {
        this.simTime = simTime;
    }
    
    /**
     * Accessor the start time
     * 
     * @return The start time
     */
    public int getStrTime() {
        return strTime;
    }
    
    /**
     * Mutator the start time
     * 
     * @param strTime
     */
    public void setStrTime(int strTime) {
        this.strTime = strTime;
    }
    
    /**
     * Accessor the life time
     * 
     * @return The life time
     */
    public int getLifeTime() {
        return lifeTime;
    }
    
    /**
     * Mutator the life time
     * 
     * @param lifeTime
     */
    public void setLifeTime(int lifeTime) {
        this.lifeTime = lifeTime;
    }
    
    /**
     * Accessor the time division
     * 
     * @return The time division
     */
    public float getTimeDiv() {
        return timeDiv;
    }
    
    /**
     * Mutator the time division
     * 
     * @param timeDiv
     */
    public void setTimeDiv(float timeDiv) {
        this.timeDiv = timeDiv;
    }
    
    /**
     * Accessor the present time
     *  
     * @return The present time
     */
    public float getPresent(){
        return present;
    }
    
    /**
     * Mutator the present time
     * 
     * @param present
     */
    public void setPresent(float present){
        this.present = present;
    }
    
    /**
     * Accessor the figures information list
     * 
     * @return The figures information list
     */
    public ArrayList<FigInform> getFigInformList() {
        return figInformList;
    }

    /**
     * Mutator the figures finformation list
     * 
     * @param figInformList
     */
    public void setFigInformList(ArrayList<FigInform> figInformList) {
        this.figInformList = figInformList;
    }

    /**
     * 실제로 시뮬레이션을 하는 메소드 입니다. SimulationProp에서 상속하여 구현해야 합니다.
     * @deprecated for packing Figures per Equipment and probably doing Thread programing replaced by {@link #simulationBody(Vector)}
     * @param figList
     * @return SimulationFields[]를 반환합니다.
     * @throws Exception
     */
    @Deprecated public abstract SimulationFields[] simulationBody(ArrayList<Figures> figList) throws Exception;
    
    /**
     * 실제로 시뮬레이션을 하는 메소드 입니다. SimulationProp에서 상속하여 구현해야 합니다.
     * 
     * @param modelingVector
     * @return SimulationFields[]를 반환합니다.
     * @throws Exception
     */
    public abstract SimulationFields[] simulationBody(Vector<Modeling> modelingVector) throws Exception;
    
    /**
     * 속성값을 넣으면 해당하는Failure Type으로 HazardRate를 계산해서 반환합니다.
     *  
     * @param Figures
     * @return Hazard Rate
     */
    public float selectFailType(Figures fig){
        float harzardRate = 0f;
        switch(fig.getFigDistribution()){
            case 1 :  harzardRate = exponentialFailType(fig.getFigFailureTime().floatValue(), fig.getFigBreakPoint().floatValue(), fig.getFigRecovFactor().floatValue(), fig.getFigRepModRelia(), fig.getFigRepRepeat(), fig.getFigRepUpto());
                      break;
            case 2 :  harzardRate = normalFailType(fig.getFigNormExptMttf().floatValue(), fig.getFigNormStdMttf().floatValue(),fig.getFigBreakPoint().floatValue(), 0f, "", "", 0);//TODO Normal
                      break;
            case 3 :  harzardRate = rectangularFailType(fig.getFigMinMttf().floatValue(),fig.getFigMaxMttf().floatValue(),fig.getFigBreakPoint().floatValue(),fig.getFigRecovFactor().floatValue(), fig.getFigRepModRelia(), fig.getFigRepRepeat(), fig.getFigRepUpto());//TODO Rectangular
                      break;
            case 4 :  harzardRate = triangularFailType(fig.getFigMinMttf().floatValue(),fig.getFigMaxMttf().floatValue(),fig.getFigMostMttf().floatValue(),fig.getFigBreakPoint().floatValue(), fig.getFigRecovFactor().floatValue(), fig.getFigRepModRelia(), fig.getFigRepRepeat(), fig.getFigRepUpto());//TODO Triangular
                      break;
            case 5 :  harzardRate = weibullTimeNoDelayType(fig.getFigWeiCharMttf().floatValue(), fig.getFigWeiShapeMttf().floatValue(), fig.getFigBreakPoint().floatValue(), fig.getFigRecovFactor().floatValue(), fig.getFigRepModRelia(), fig.getFigRepRepeat(), fig.getFigRepUpto());
                      break;
            case 6 :  harzardRate = weibullTimeDelayType(fig.getFigWeiCharMttf().floatValue(), fig.getFigWeiShapeMttf().floatValue(), fig.getFigBreakPoint().floatValue(), fig.getFigRecovFactor().floatValue(),0f, fig.getFigRepModRelia(), fig.getFigRepRepeat(), fig.getFigRepUpto());//TODO Weibull Time Delay
                      break;
            default: harzardRate = 0f;//TODO
                      break;
            
        }
        //log.debug(harzardRate);
        return harzardRate;
    };

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Override
    public float demandLoadFailType(float reliability, float failureTime) {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float exponentialFailType(float failureTime, float breakPoint, float reductionFactor, String repMod, String repRepeat, int repUpto) {
        float exponentialHarzardRate = 0f;
        exponentialHarzardRate = (1/failureTime)/timeDiv;
        
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                
                //Up to Number is
                if("N".equals(repRepeat)){
                
                //Infinity is
                }else if("Y".equals(repRepeat)){
                    
                    if(reductionFactor > 0f){
                        exponentialHarzardRate = exponentialHarzardRate / reductionFactor;
                    }
                    
                }
                
            }
        }
        
        return exponentialHarzardRate;
        
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float normalFailType(float expectedValue, float standardDeviation, float breakPoint, float reductionFactor, String repMod, String repRepeat, int repUpto) {
        double normalHazardRate = 0d;
        double presentLife = 0d;
        
        if(breakPoint > 0f){
            presentLife = getPresent() - breakPoint;
        }else{
            presentLife = getPresent();
        }
        
        NormalDistribution normDist = new NormalDistribution(expectedValue, standardDeviation);
        normalHazardRate = (normDist.density(presentLife)/(1-normDist.cumulativeProbability(presentLife)))/timeDiv;
        
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                //Up to Number is
                if("N".equals(repRepeat)){
                
                //Infinity is
                }else if("Y".equals(repRepeat)){
                    if(reductionFactor > 0f){
                        normalHazardRate = normalHazardRate / reductionFactor;
                    }
                }
            }
        }
        return (float)normalHazardRate;
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public float rectangularFailType(float minTtf, float maxTtf, float breakPoint, float reductionFactor, String repMod, String repRepeat, int repUpto) {
        float rectangularHazardRate = 0f;

        //Initialization of present with break time
        minTtf += breakPoint;
        maxTtf += breakPoint;
        
        //Make Hazard Rate
        if(minTtf <= getPresent() && maxTtf >= getPresent()){
            rectangularHazardRate = (1/(maxTtf-getPresent()))/timeDiv;
        }else if(maxTtf <= getPresent()){
            rectangularHazardRate = 1f;
        }
        
        //Impact reduction factor
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                //Up to Number is
                if("N".equals(repRepeat)){
                
                }
            
                //Infinity is
                if("Y".equals(repRepeat)){
                    if(reductionFactor > 0f){
                        rectangularHazardRate = rectangularHazardRate / reductionFactor;
                    }
                }
            }
        }
        return rectangularHazardRate;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public float triangularFailType(float minTtf, float maxTtf, float mostTtf, float breakPoint, float reductionFactor, String repMod, String repRepeat, int repUpto) {
        double triangularHazardRate = 0d;
        
        //Initialization of present with break time
        minTtf += breakPoint;
        maxTtf += breakPoint;
        mostTtf += breakPoint;
        
        //Make Hazard Rate
        if(minTtf <= getPresent() && mostTtf >= getPresent()){
            triangularHazardRate = (2*(getPresent()-minTtf))/((maxTtf-minTtf)*(mostTtf-minTtf)-Math.pow((getPresent()-minTtf), 2))/timeDiv;
        }else if(mostTtf < getPresent() && maxTtf >= getPresent()){
            triangularHazardRate = 2/(maxTtf-getPresent())/timeDiv;
        }else if (maxTtf < getPresent()){
            triangularHazardRate = 1d/timeDiv;
        }
        
        //Impact reduction factor
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                //Up to Number is
                if("N".equals(repRepeat)){
                
                }
                
                //Infinity is
                if("Y".equals(repRepeat)){
                    if(reductionFactor > 0f){
                        triangularHazardRate = triangularHazardRate / reductionFactor;
                    }
                }
            }
        }
        
        return (float)triangularHazardRate;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public float weibullTimeNoDelayType(float characteristicLife, float shapeParameter, float breakPoint, float reductionFactor, String repMod, String repRepeat, int repUpto) {
        // TODO Auto-generated method stub
        float presentLife = 0f;
        float maintenanceIntv = 0f;
        
        //고장시점이 존재한다면
        if(breakPoint > 0f){
            //일정한 수리 시간이 있다면
            if(maintenanceIntv > 0f){
                if(((getPresent()%maintenanceIntv)<maintenanceIntv)&&(((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)<(getPresent()%maintenanceIntv))){
                    presentLife = ((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)-0f;
                }else{
                    presentLife = getPresent()-breakPoint;
                }
            //일정한 수리 식나이 없다면
            }else{
                if(breakPoint > getPresent()){
                    setPresent(breakPoint);
                }
                presentLife = getPresent()-breakPoint;
            }
        //고장시점이 존재하지 않는다면
        }else{
            //일정한 수리 시간이 있다면
            if(maintenanceIntv > 0f){
                if(((getPresent()%maintenanceIntv)<maintenanceIntv)&&(((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)<(getPresent()%maintenanceIntv))){
                    presentLife = ((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)-0f;
                    breakPoint = getPresent();
                }else{
                    presentLife = getPresent()-breakPoint;
                }
            //일정한 수리시간이 없다면
            }else{
                presentLife = (getPresent())-0f;
            }
        }
        
        double hr = (shapeParameter/ (characteristicLife))*Math.pow((presentLife/ (characteristicLife)), shapeParameter -1f);
        
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                //Up to Number is
                if("N".equals(repRepeat)){
                
                //Infinity is
                }else if("Y".equals(repRepeat)){
                    if(reductionFactor > 0f){
                        hr = hr / reductionFactor;
                    }
                }
            }
        }
        return (float)hr/timeDiv;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float weibullTimeDelayType(float characteristicLife, float shapeParameter, float breakPoint, float reductionFactor , float timeDelay, String repMod, String repRepeat, int repUpto) {
        double hr = 0;
        float presentLife = 0f;
        float maintenanceIntv = 0f;
        
        //고장시점이 존재한다면
        if(breakPoint > 0f){
            //일정한 수리 시간이 있다면
            if(maintenanceIntv > 0f){
                if(((getPresent()%maintenanceIntv)<maintenanceIntv)&&(((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)<(getPresent()%maintenanceIntv))){
                    presentLife = ((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)-0f;
                }else{
                    presentLife = getPresent()-breakPoint;
                }
            //일정한 수리 식나이 없다면
            }else{
                if(breakPoint > getPresent()){
                    setPresent(breakPoint);
                }
                presentLife = getPresent()-breakPoint;
            }
        //고장시점이 존재하지 않는다면
        }else{
            //일정한 수리 시간이 있다면
            if(maintenanceIntv > 0f){
                if(((getPresent()%maintenanceIntv)<maintenanceIntv)&&(((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)<(getPresent()%maintenanceIntv))){
                    presentLife = ((getPresent()+(1f/getTimeDiv()))%maintenanceIntv)-0f;
                    breakPoint = getPresent();
                }else{
                    presentLife = getPresent()-breakPoint;
                }
            //일정한 수리시간이 없다면
            }else{
                presentLife = (getPresent())-0f;
            }
        }
        
        timeDelay += breakPoint;
        
        if(getPresent() > timeDelay){
            hr = (shapeParameter/ (characteristicLife))*Math.pow((presentLife/ (characteristicLife)), shapeParameter -1f);
        }
        
        if(breakPoint>0f){
            //Use the reduction Factor or Not
            if("Y".equals(repMod)){
                //Up to Number is
                if("N".equals(repRepeat)){
                
                //Infinity is
                }else if("Y".equals(repRepeat)){
                    if(reductionFactor > 0f){
                        hr = hr / reductionFactor;
                    }
                }
            }
        }
        
        return (float)hr/timeDiv;
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Override
    public float weibullAgeAtStartupType(float characteristicLife, float shapeFactor, float ageAtStartOfSim, String repMod, String repRepeat, int repUpto) {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Override
    public float weibullTimeDelayOn1stFailType(float characteristicLife, float shapeFactor, float timeDelayToStart1stFailDist, String repMod, String repRepeat, int repUpto) {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Override
    public float noneFailType() {
        // TODO Auto-generated method stub
        return 0;
    }
    
    //=======================RepairType Compose Start======================//

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Override
    public float nonDeliveryRepairType() {
        // TODO Auto-generated method stub
        return 0;
    }
    
    
    //=======================Util for Simulation Start======================//
    public void returnHazardRandom(String[] equipInform){
        for(String eqInfo : equipInform){
            String[] info = eqInfo.split(",");
            FigInform figInform = new FigInform();

            figInform.setFigUid(Integer.parseInt(info[0]));
            figInform.setHazardRate(Float.parseFloat(info[1]));
            figInform.setRandomNum(Float.parseFloat(info[2]));
            
            figInformList.add(figInform);
            figInform = null;
        }
        
    }

}
